package org.sword.wechat4j.api;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.sword.wechat4j.api.request.Cgi_binTokenGet;
import org.sword.wechat4j.api.request.ApiRequest;
import org.sword.wechat4j.api.response.FileResponse;
import org.sword.wechat4j.util.Config;
import org.sword.wechat4j.util.SingletonHttpClient;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信 api 客户端
 * Created by sean on 2016-07-17-0017.
 */
public class WechatClient {

    private SingletonHttpClient httpClient = SingletonHttpClient.getDefaultClient();
    private Config config = Config.instance();

    private String host = config.getUrl();
    private String appid = config.getAppid();
    private String secret = config.getAppSecret();
    private String token;

    /**
     * 此构造仅仅在使用配置文件下可用
     */
    public WechatClient() {
    }

    /**
     * 此构造用于一般API调用，
     *  也可用之获取access_token，但必须在Cgi_binTokenGet请求中设置好相关参数。
     * @param host API服务器host
     * @param token access_token
     */
    public WechatClient(String host, String token) {
        this.host = host;
        this.token = token;
    }

    /**
     * 此构造设置全部需要的参数，一般并不常用
     * @param host API服务器host
     * @param appid appid
     * @param secret secret
     * @param token access_token
     */
    public WechatClient(String host, String appid, String secret, String token) {
        this.host = host;
        this.token = token;
        this.appid = appid;
        this.secret = secret;
    }


    /**
     * 如果确定之后不会用微信API了，可以调用销毁方法释放资源，调用该方法后再调用API必会失败
     */
    public void destory() {
        if (httpClient != null)
            httpClient.shutdown();
    }

    /**
     * 请求API（需要accessToken）
     *
     * @param request     api请求
     * @param accessToken 令牌
     * @param <T>         响应类型
     * @return 响应
     * @throws IOException
     */
    public <T> T execute(ApiRequest<T> request, String accessToken) throws IOException {
        String result = "";
        Class<? extends ApiRequest> clazz = request.getClass();
        String requestName = clazz.getSimpleName();

        String baseUrl = buildBaseUrl(request);
        if (requestName.toLowerCase().endsWith("post")) {
            if (!StringUtils.isEmpty(accessToken))
                baseUrl += "?access_token=" + accessToken;
            if (requestName.toLowerCase().endsWith("filepost"))
                result = httpClient.post(baseUrl, createPostData(request));
            else
                result = httpClient.post(baseUrl, JSON.toJSONString(request));//必须json格式
        } else if (requestName.toLowerCase().endsWith("get")) {
            baseUrl += "?" + createGetData(request);
            if (!StringUtils.isEmpty(accessToken))
                baseUrl += "&access_token=" + accessToken;
            result = httpClient.get(baseUrl);
        } else if (requestName.toLowerCase().endsWith("getfile")) {
            FileResponse fileResponse = new FileResponse();
            HttpEntity entity = httpClient.getStream(baseUrl);
            if (entity.isStreaming()) {
                fileResponse.setErrcode(0L);
                fileResponse.setIn(entity.getContent());
            } else {
                fileResponse = JSON.parseObject(EntityUtils.toString(entity, "utf-8"), FileResponse.class);
            }

            return (T) fileResponse;
        }
        return JSON.parseObject(result, getResponseType(clazz));
    }

    /**
     * 请求API（不需要accessToken）
     *
     * @param request api请求
     * @param <T>     响应类型
     * @return 响应
     * @throws IOException
     */
    public <T> T execute(ApiRequest<T> request) throws IOException {
        return execute(request, token);
    }

    private String createGetData(ApiRequest request) {
        Map<String, Object> data = createPostData(request);
        if (data.size() == 0) return "";
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            sb.append("&" + entry.getKey() + "=" + entry.getValue());
        }
        return sb.substring(1);
    }

    private Map<String, Object> createPostData(ApiRequest request) {
        Map<String, Object> data = new HashMap<>();
        Class<? extends ApiRequest> clazz = request.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            String name = field.getName();

            if (name.equals("appid") && !StringUtils.isEmpty(this.appid)) {
                data.put(name, this.appid);
                continue;
            } else if (name.equals("secret") && !StringUtils.isEmpty(this.secret)) {
                data.put(name, this.secret);
                continue;
            }
            field.setAccessible(true);

            Object value = null;
            try {
                value = field.get(request);
            } catch (Exception e) {
            }
            if (value != null) {
                if (value instanceof File) {
                    data.put(name, value);
                    continue;
                }
                String jv = JSON.toJSONString(value);
                if (!jv.contains("{"))
                    jv = jv.replace("\"", "");
                data.put(name, jv);
            }
        }
        return data;
    }

    private String buildBaseUrl(ApiRequest request) {
        Class<? extends ApiRequest> clazz = request.getClass();
        String name = clazz.getSimpleName().toLowerCase();
        String host = this.host;
        if (name.endsWith("httpget") || name.endsWith("httppost"))
            host = this.host.replace("https://", "http://");
        return host + parseRequest(clazz.getSimpleName());
    }

    private String parseRequest(String str) {
        char[] ch = str.toCharArray();
        String apiName = "";
        int tmp = 0;
        for (int i = 1; i < ch.length; i++) {
            if ((int) ch[i] >= 65 && (int) ch[i] <= 90) {
                apiName += "/" + str.substring(tmp, i).toLowerCase();
                tmp = i;
            }
        }
        return apiName.replace("cgi_bin", "cgi-bin").replace("$", ".").replace("_1_", ".");
    }

    private Type getResponseType(Class<? extends ApiRequest> clazz) {
        return ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
    }

}
